package com.gp.billsrecord.vm

import android.app.Application
import android.graphics.Bitmap
import android.os.SystemClock
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.liveData
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.cachedIn
import androidx.room.withTransaction
import com.gp.billsrecord.base.BaseViewModel
import com.gp.billsrecord.base.Const
import com.gp.billsrecord.model.data.*
import com.gp.billsrecord.model.room.AppDatabase
import com.gp.billsrecord.util.DateUtil
import com.xujichang.base.extensions.Logs
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.util.concurrent.atomic.AtomicLong

class AppViewModel(application: Application) : BaseViewModel(application) {
    companion object {
        private const val TAG = "AppViewModel"
    }

    private val _chartBillFlows = MutableLiveData<List<BillFlowInfo>>()
    val chartBillFlows: LiveData<List<BillFlowInfo>> get() = _chartBillFlows
    private val errorHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
        Logs.d(TAG, "error ${throwable.localizedMessage}")
    }
    private val database = AppDatabase.get(application)

    fun observeAccounts() = Pager(PagingConfig(Const.PAGE_SIZE)) {
        //用户ID
        runBlocking {
            val userId = obtainUserId() ?: throw RuntimeException("userId获取失败")
            database.accountDao().observeAccounts(userId)
        }
    }.flow.cachedIn(viewModelScope)

    fun observeAccountTypes() = liveData<List<AccountType>> {
        val userId = obtainUserId() ?: throw RuntimeException("userId获取失败")
        emitSource(database.accountDao().observeAccountTypes(userId))
    }

    /**
     * 添加账户
     */
    fun addAccount(number: String, balance: Long, type: AccountType) = liveData(errorHandler) {
        val accountDao = database.accountDao()
        val userId = obtainUserId() ?: throw RuntimeException("userId获取失败")
        val result = database.withTransaction {
            //获取目前存在的最新的数据
            val latestAccountId = accountDao.obtainLatest()?.accountId ?: Const.ACCOUNT_ID_START
            database.accountDao().addAccount(
                AccountInfo(
                    latestAccountId + 1,
                    number,
                    type.typeId,
                    type.typeName,
                    System.currentTimeMillis(),
                    balance,
                    userId
                )
            )
        }
        emit(result != -1L)
    }

    /**
     * 对流水做筛选
     */
    fun doBillFlowFilter(date: Long, types: List<Long>) = Pager(PagingConfig(Const.PAGE_SIZE)) {
        runBlocking {
            //当前选中月份的开始时间戳
            val timeStart = DateUtil.parseMonthStart(date)
            //当前选中月份的结束时间戳
            val timeEnd = DateUtil.parseMonthEnd(date)
            //uerid
            val userId = obtainUserId() ?: throw RuntimeException("userId获取失败")
            if (types.isNullOrEmpty()) {
                database.billFlowDao().filterDate(timeStart, timeEnd, userId)
            } else {
                database.billFlowDao()
                    .filterDateAndTag(timeStart, timeEnd, types, userId)
            }
        }
    }.flow.cachedIn(viewModelScope)

    /**
     * 取数据库中的消费类型
     */
    fun observeConsumptionTypes() = liveData<List<ConsumptionType>> {
        val userId = obtainUserId() ?: throw RuntimeException("user Id 获取失败")
        emitSource(database.consumptionDao().observeConsumptionTypes(userId))
    }

    /**
     * 添加流水
     */
    fun addBillFlow(billFlow: BillFlowInfo) = liveData(errorHandler) {
        val billFlowDao = database.billFlowDao()
        val accountDao = database.accountDao()
        val result = database.withTransaction {
            val userId = obtainUserId() ?: throw RuntimeException("user Id 获取失败")
            //金额做加减
            accountDao.updateBalance(userId, billFlow.accountId, billFlow.remainMoney)
            //获取最新的一条记录的ID
            val latestFlowId = billFlowDao.obtainLatest() ?: Const.BILL_FLOW_START
            //ID + 1作为新的数据的ID
            billFlow.flowId = latestFlowId + 1
            billFlow.userId = userId
            //添加一条流水记录
            billFlowDao.addBillFlow(billFlow)
        }
        emit(result != -1L)
    }

    fun obtainLogonInfo() = liveData {
        //用户ID
        val userId = obtainUserId() ?: throw RuntimeException("userId 获取失败")
        //用户信息
        emitSource(database.logonUserDao().observerUserInfo(userId))
    }

    fun observeAccountList() = liveData {
        val userId = obtainUserId() ?: throw RuntimeException("userId 获取失败")
        emitSource(database.accountDao().observeAccountList(userId))
    }

    fun observeAccountsLiveData() = liveData<List<AccountInfo>> {
        val userId = obtainUserId() ?: throw RuntimeException("userId 获取失败")
        emitSource(database.accountDao().observeAccountList(userId))
    }

    /**
     *更新用户头像
     */
    fun updateAvatar(bitmap: Bitmap) = viewModelScope.launch(Dispatchers.IO) {
        //保存图片到缓存文件夹
        val cacheDir = getApplication<Application>().cacheDir
        //根据时间戳生成文件名
        val fileName = "IMAGE_${SystemClock.uptimeMillis()}.jpg"
        //把图片数据存储到文件
        val imgFile = File(cacheDir, fileName)
        saveBitmapToFile(imgFile, bitmap)
        //更新数据库
        val userId = obtainUserId() ?: throw  RuntimeException("userId 获取失败")
        database.logonUserDao().updateAvatar(userId, imgFile.absolutePath)
    }

    /**
     * 图片数据存储到文件
     */
    private fun saveBitmapToFile(file: File, bitmap: Bitmap) {
        if (!file.exists()) {
            file.createNewFile()
        }
        val bos = ByteArrayOutputStream()
        bitmap.compress(Bitmap.CompressFormat.JPEG, 90, bos)
        FileOutputStream(file).use {
            it.write(bos.toByteArray())
        }
    }

    fun doBillFlowFilterAsList(timeMs: Long, types: List<Long>) =
        viewModelScope.launch {
            val timeStart = DateUtil.parseMonthStart(timeMs)
            val timeEnd = DateUtil.parseMonthEnd(timeMs)
            val userId = obtainUserId() ?: throw RuntimeException("userId获取失败")
            val list = if (types.isNullOrEmpty()) {
                database.billFlowDao().filterDateAsLivaData(timeStart, timeEnd, userId)
            } else {
                database.billFlowDao()
                    .filterDateAndTagAsLiveData(timeStart, timeEnd, types, userId)
            }
            _chartBillFlows.postValue(list)
        }
}
